package com.ruoyi.spot.openapi.kline.controller;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.enums.KlineTypeEnum;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.spot.constants.SpotMonitorConstants;
import com.ruoyi.spot.openapi.kline.domain.SpotOpenapiKline;
import com.ruoyi.spot.openapi.kline.domain.VO.SpotOpenapiKlineVO;
import com.ruoyi.spot.openapi.kline.service.ISpotOpenapiKlineService;
import com.ruoyi.util.LarkDangerRobotUtils;
import com.ruoyi.util.LarkRecoverRobotUtils;
import com.ruoyi.util.MergeUtil;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.*;

/**
 * K线/蜡烛图Controller (精进版)
 *
 * @author saumell
 * @date 2024-08-12
 */
@RestController
@RequestMapping("/sp_api_Kline/sp_api_Kline_bak")
public class SpotOpenapiKlineController extends BaseController {

    private static final Logger log = LoggerFactory.getLogger(SpotOpenapiKlineController.class);

    @Autowired
    private ISpotOpenapiKlineService spotOpenapiKlineService;

    @Value("${my.lark.dangerlark}")
    private String dangerlark;

    @Value("${my.lark.recoverlark}")
    private String recoverlark;

    private static final Map<String, String> INTERVAL_CORRECTION_MAP;
    static {
        Map<String, String> map = new HashMap<>();
        map.put("1min", "1m");
        map.put("5min", "5m");
        map.put("15min", "15m");
        map.put("30min", "30m");
        map.put("60min", "1h");
        map.put("1day", "1d");
        map.put("1week", "1w");
        map.put("1month", "1M");
        INTERVAL_CORRECTION_MAP = Collections.unmodifiableMap(map);
    }

    @PreAuthorize("@ss.hasPermi('sp_api_Kline:sp_api_Kline_bak:list')")
    @GetMapping("/list")
    public TableDataInfo list(SpotOpenapiKlineVO spotOpenapiKline) {
        startPage();
        List<SpotOpenapiKlineVO> list = spotOpenapiKlineService.selectSpotOpenapiKlineList(spotOpenapiKline);
        return getDataTable(list);
    }

    @PreAuthorize("@ss.hasPermi('sp_api_Kline:sp_api_Kline_bak:export')")
    @Log(title = "K线/蜡烛图", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, SpotOpenapiKlineVO spotOpenapiKline) {
        List<SpotOpenapiKlineVO> list = spotOpenapiKlineService.selectSpotOpenapiKlineList(spotOpenapiKline);
        ExcelUtil<SpotOpenapiKlineVO> util = new ExcelUtil<SpotOpenapiKlineVO>(SpotOpenapiKlineVO.class);
        util.exportExcel(response, list, "K线_蜡烛图数据");
    }

    /**
     * 校验K线数据（精进版：错误信息包含时间戳和价格）
     */
    private static String checkoutMessage(String openapiMessage, String timstampname, Long timestamp) {
        try {
            String trimmedResponse = openapiMessage.trim();

            if (trimmedResponse.startsWith("{")) {
                try {
                    com.alibaba.fastjson2.JSONObject errorObject = JSON.parseObject(trimmedResponse);
                    String apiErrorMsg = errorObject.getString("msg");
                    return "false, API返回错误信息: " + (apiErrorMsg != null ? apiErrorMsg : trimmedResponse);
                } catch (Exception parseException) {
                    return "false, API返回了无法解析的错误对象: " + trimmedResponse;
                }
            }

            JSONArray jsonArray = JSON.parseArray(trimmedResponse);
            if (jsonArray == null || jsonArray.isEmpty()) {
                return "true";
            }

            StringBuilder errors = new StringBuilder();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            boolean timestampOrderErrorLogged = false;
            boolean timestampGapErrorLogged = false;
            boolean priceGapErrorLogged = false;

            long previousIdx = -1;
            BigDecimal previousClose = null;

            for (int i = 0; i < jsonArray.size(); i++) {
                Object element = jsonArray.get(i);
                if (!(element instanceof JSONArray) || ((JSONArray) element).size() < 6) {
                    errors.append(String.format("第 %d 条K线结构不正确; ", i + 1));
                    continue;
                }
                JSONArray currentKline = (JSONArray) element;

                long currentIdx = currentKline.getLong(0);
                BigDecimal currentOpen = currentKline.getBigDecimal(1);
                BigDecimal currentHigh = currentKline.getBigDecimal(2);
                BigDecimal currentLow = currentKline.getBigDecimal(3);
                BigDecimal currentClose = currentKline.getBigDecimal(4);
                String readableTime = sdf.format(new Date(currentIdx));

                // 校验1: OHLC价格逻辑 (高>=开/收, 低<=开/收)
                if (currentHigh.compareTo(currentOpen) < 0 || currentHigh.compareTo(currentClose) < 0 ||
                        currentLow.compareTo(currentOpen) > 0 || currentLow.compareTo(currentClose) > 0) {
                    errors.append(String.format("价格逻辑错误 at %s (O:%s H:%s L:%s C:%s); ", readableTime, currentOpen, currentHigh, currentLow, currentClose));
                }

                if (i > 0) {
                    // 校验 2: 时间戳是否严格递增
                    if (!timestampOrderErrorLogged && currentIdx <= previousIdx) {
                        errors.append(String.format("时间戳排序错误 at %s; ", readableTime));
                        timestampOrderErrorLogged = true;
                    }

                    // 校验 3: 时间戳是否存在间隔（跳空）
                    if (!timestampGapErrorLogged && (currentIdx - previousIdx) != timestamp) {
                        errors.append(String.format("时间跳空 at %s (间隔非%s); ", readableTime, timstampname));
                        timestampGapErrorLogged = true;
                    }

                    // 校验 4: 上一根的收盘价是否等于这一根的开盘价（价格跳空）
                    if (!priceGapErrorLogged && previousClose != null && previousClose.compareTo(currentOpen) != 0) {
                        errors.append(String.format("价格跳空 at %s (前收盘:%s, 现开盘:%s); ", readableTime, previousClose, currentOpen));
                        priceGapErrorLogged = true;
                    }
                }
                previousIdx = currentIdx;
                previousClose = currentClose;
            }

            if (errors.length() > 0) {
                return "false, " + errors.toString();
            }

            return "true";
        } catch (Exception e) {
            log.error("checkoutMessage 发生致命异常", e); // 使用log.error记录完整堆栈
            return "false, K线数据校验时发生致命异常: " + e.getMessage();
        }
    }


    public void SpotOpenapiKline() {
        List<SpotOpenapiKlineVO> spotOpenAPIList = spotOpenapiKlineService.selectSpotAllData();
        List<Integer> dangerProductionInspections = new ArrayList<Integer>();
        List<Integer> recoverProductionInspections = new ArrayList<Integer>();
        for (SpotOpenapiKlineVO spotOpenapiKlineVO : spotOpenAPIList) {
            Integer id = spotOpenapiKlineVO.getId();
            String currencyPair = spotOpenapiKlineVO.getCurrencyPair();
            String callingState = spotOpenapiKlineVO.getCallingState();
            Date lastExecutionTime = spotOpenapiKlineVO.getLastExecutionTime();
            Integer successTimes = spotOpenapiKlineVO.getSuccessTimes();
            Integer failTimes = spotOpenapiKlineVO.getFailTimes();
            Integer dangerWarning = spotOpenapiKlineVO.getDangerWarning();
            Integer safeWarning = spotOpenapiKlineVO.getSafeWarning();
            String openapirequest = null;
            OkHttpClient client = new OkHttpClient().newBuilder().build();
            List<String> chekoutList = new ArrayList<>();
            try {
                for (KlineTypeEnum value : KlineTypeEnum.values()) {
                    String incorrectInterval = value.getType();
                    String correctInterval = INTERVAL_CORRECTION_MAP.getOrDefault(incorrectInterval, incorrectInterval);

                    Request request = new Request.Builder()
                            .url(MessageFormat.format(SpotMonitorConstants.SPOT_OPENAPI_KLINE, currencyPair, correctInterval, 100))
                            .get()
                            .build();
                    Response response = client.newCall(request).execute();
                    String responseBody = response.body().string();
                    openapirequest = checkoutMessage(responseBody, value.getType(), value.getTimestamp());
                    chekoutList.add(openapirequest);
                }
            } catch (Exception e) {
                // 记录请求阶段的IO等异常
                log.error("API请求执行异常 for " + currencyPair, e);
                // 确保即使请求失败，也有一个明确的失败结果进入列表
                chekoutList.add("false, API请求阶段异常: " + e.getMessage());
            }

            String reponseresult = MergeUtil.mergerespont(chekoutList);

            // ... 后续所有业务逻辑保持不变 ...
            if (reponseresult == null || reponseresult.isEmpty() || !reponseresult.startsWith("true") && !reponseresult.startsWith("false")) {
                // 处理未知或空的合并结果
                log.error("MergeUtil 返回了未知结果: " + reponseresult);
                reponseresult = "false, 内部结果合并异常";
            }

            if (reponseresult.startsWith("false")) {
                String reason = reponseresult.substring(7); // 提取 "false, " 之后的原因
                if (Objects.isNull(callingState) || callingState.isEmpty()) {
                    // 首次失败
                    LocalDateTime currentDateTime = LocalDateTime.now();
                    lastExecutionTime = Timestamp.valueOf(currentDateTime);
                    successTimes = 0;
                    failTimes = 1;
                    Integer currencysId = spotOpenapiKlineService.queryCurryNumber(currencyPair);
                    SpotOpenapiKline o1 = new SpotOpenapiKline(id, currencysId, "off_line", lastExecutionTime, successTimes, failTimes, reason, null, null, null);
                    spotOpenapiKlineService.insertSpotOpenapiKline(o1);
                    dangerProductionInspections.add(o1.getId());
                    log.info("[现货-OpenaAPI-Kline]：【{}】首次调用即失败, 原因: {}", currencyPair, reason);
                } else if ("on_line".equals(callingState)) {
                    // 从成功转为失败
                    dangerProductionInspections.add(id);
                    callingState = "off_line";
                    LocalDateTime currentDateTime = LocalDateTime.now();
                    lastExecutionTime = Timestamp.valueOf(currentDateTime);
                    failTimes = (failTimes == null ? 0 : failTimes) + 1;
                    SpotOpenapiKline o1 = new SpotOpenapiKline(id, null, callingState, lastExecutionTime, successTimes, failTimes, reason, null, null, null);
                    spotOpenapiKlineService.updateSpotOpenapiKline(o1);
                    log.info("[现货-OpenaAPI-Kline]：【{}】从在线转为失败, 原因: {}", currencyPair, reason);
                } else { // "off_line"
                    // 持续失败
                    if (dangerWarning == null || dangerWarning == 1) {
                        dangerProductionInspections.add(id);
                    }
                    LocalDateTime currentDateTime = LocalDateTime.now();
                    lastExecutionTime = Timestamp.valueOf(currentDateTime);
                    failTimes = (failTimes == null ? 0 : failTimes) + 1;
                    SpotOpenapiKline o1 = new SpotOpenapiKline(id, null, callingState, lastExecutionTime, successTimes, failTimes, reason, null, null, null);
                    spotOpenapiKlineService.updateSpotOpenapiKline(o1);
                    log.info("[现货-OpenaAPI-Kline]：【{}】持续失败, 原因: {}", currencyPair, reason);
                }
            } else { // 结果为 "true"
                if ("off_line".equals(callingState)) {
                    // 从失败恢复
                    if (safeWarning == null || safeWarning == 1) {
                        recoverProductionInspections.add(id);
                    }
                    callingState = "on_line";
                    LocalDateTime currentDateTime = LocalDateTime.now();
                    lastExecutionTime = Timestamp.valueOf(currentDateTime);
                    successTimes = (successTimes == null ? 0 : successTimes) + 1;
                    SpotOpenapiKline o1 = new SpotOpenapiKline(id, null, callingState, lastExecutionTime, successTimes, failTimes, null, null, null, null);
                    spotOpenapiKlineService.updateSpotOpenapiKline(o1);
                    log.info("[现货-OpenaAPI-Kline]：【{}】从失败中恢复成功", currencyPair);
                } else { // "on_line" 或首次成功
                    if (Objects.isNull(callingState) || callingState.isEmpty()) {
                        // 首次成功
                        successTimes = 1;
                        failTimes = 0;
                        Integer currencysId = spotOpenapiKlineService.queryCurryNumber(currencyPair);
                        SpotOpenapiKline o1 = new SpotOpenapiKline(id, currencysId, "on_line", Timestamp.valueOf(LocalDateTime.now()), successTimes, failTimes, null, null, null, null);
                        spotOpenapiKlineService.insertSpotOpenapiKline(o1);
                        log.info("[现货-OpenaAPI-Kline]：【{}】首次巡检成功", currencyPair);
                    } else {
                        // 持续成功
                        successTimes = (successTimes == null ? 0 : successTimes) + 1;
                        SpotOpenapiKline o1 = new SpotOpenapiKline(id, null, "on_line", Timestamp.valueOf(LocalDateTime.now()), successTimes, failTimes, null, null, null, null);
                        spotOpenapiKlineService.updateSpotOpenapiKline(o1);
                        log.info("[现货-OpenaAPI-Kline]：【{}】持续巡检成功", currencyPair);
                    }
                }
            }
        }

        List<SpotOpenapiKlineVO> spotKlineCurrencysList_new = spotOpenapiKlineService.selectSpotAllData();
        List<SpotOpenapiKlineVO> allProductionInspections2 = new ArrayList<>();
        List<SpotOpenapiKlineVO> allProductionInspections3 = new ArrayList<>();
        for (SpotOpenapiKlineVO vo : spotKlineCurrencysList_new) {
            Integer id = vo.getId();
            if (dangerProductionInspections.contains(id)) {
                allProductionInspections2.add(vo);
            } else if (recoverProductionInspections.contains(id)) {
                allProductionInspections3.add(vo);
            }
        }

        if (!allProductionInspections2.isEmpty()) {
            StringBuilder noticeBuilder = new StringBuilder("【现货-Kline-OpenaAPI】下方为行情K线/蜡烛图数据故障的交易对名称及调用时间，请您关注！\n");
            for (SpotOpenapiKlineVO vo : allProductionInspections2) {
                String formattedDate = vo.getLastExecutionTime() != null ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(vo.getLastExecutionTime()) : "N/A";
                noticeBuilder.append(String.format("交易对名称: %s, 故障时间: %s, 故障原因: %s\n", vo.getCurrencyPair(), formattedDate, vo.getLastFailureReason()));
            }
            String dangernotice = LarkDangerRobotUtils.notice(noticeBuilder.toString(), dangerlark);
            boolean success = "lark_success".equals(dangernotice);
            for (SpotOpenapiKlineVO vo : allProductionInspections2) {
                String statusMessage = success ? "Lark发送故障信息成功！" : "Lark发送故障信息失败！";
                SpotOpenapiKline o2 = new SpotOpenapiKline(vo.getId(), null, null, null, null, null, null, success ? 0 : 1, null, statusMessage);
                spotOpenapiKlineService.updateSpotOpenapiKline(o2);
            }
        }

        if (!allProductionInspections3.isEmpty()) {
            StringBuilder noticeBuilder = new StringBuilder("【现货-Kline-OpenaAPI】下方为行情K线/蜡烛图数据恢复的交易对名称及调用时间，请您关注！\n");
            for (SpotOpenapiKlineVO vo : allProductionInspections3) {
                String formattedDate = vo.getLastExecutionTime() != null ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(vo.getLastExecutionTime()) : "N/A";
                noticeBuilder.append(String.format("交易对名称: %s, 恢复时间: %s\n", vo.getCurrencyPair(), formattedDate));
            }
            String recovernotice = LarkRecoverRobotUtils.notice(noticeBuilder.toString(), recoverlark);
            boolean success = !"lark_fail".equals(recovernotice); // 假设非fail即success
            for (SpotOpenapiKlineVO vo : allProductionInspections3) {
                String statusMessage = success ? "Lark发送恢复信息成功！" : "Lark发送恢复信息失败！";
                SpotOpenapiKline o2 = new SpotOpenapiKline(vo.getId(), null, null, null, null, null, null, null, success ? 0 : 1, statusMessage);
                spotOpenapiKlineService.updateSpotOpenapiKline(o2);
            }
        }
        log.info("============【【现货-OpenaAPI-Kline】执行完成============");
    }
}